#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G07 -- S10 ------------------------------------------------------------##
__all__ = []

from .. import autostacks
from .. import comparecards as cmp_cards
from .. import pilelib as stack
from .. import stackfill as fill
from ..gamelib import info as gi, state, game
from ..formlib import Layout
from ..hint import AbstractHint, CautiousHintMethods
from ..packlib import info as cs, place
from ..pilelib import move as a_move
from ..utillib.misc import kwdefault

GT, SL = gi.GT, gi.SL
StackWrapper = stack.StackWrapper


class Hint(AbstractHint):

    def compute_hints(self):
        """
        compute_hints() -> None

        Calculate and score possible moves.

        Override main hint intelligence.
          - **dropstacks** drop/build **rows**
          - **rows** build **reserves**.
          - **reserves** build/play on another.
        """
        assert self._stacks['set'].get('tableaux')
        drop_group = self._stacks['grp']['dropstacks']
        rows = self._stacks['set']['tableaux']
        reserves = self._stacks['grp']['reservestacks']
        # 1) **dropstacks** drop/build **rows**
        self.step010(drop_group, rows)
        # 2) **rows** build **reserves**.
        if not self._hints:
            self.step040(rows, reserves)
        # 3) **reserves** build/play on another.
        if not self._hints:
            args = self.pile_builds(from_stacks=reserves, to_stacks=reserves)
            if args:
                self._score = 60000 + 100 * (self.K - pile[0]['rank'])
                self.add_hint(ncards, from_stack, to_stack)

    def step010(self, dropstacks, rows):
        """
        step010(dropstacks:tup, rows:tup) -> None

        Calculate and score **dropstacks** drop/build **rows**. (60000 .. 99999)

        Override **dropstacks** drop/build **rows**, score based on card value
        or **step010_move_pile** then **add_hint** for each possible move.
        """
        foundations = self._stacks['set']['foundations']
        for from_stack in dropstacks:
            to_stack, ncards = from_stack.canDropCards(foundations)
            if to_stack and ncards == 1:
                card = from_stack.TOPCARD
                assert to_stack is not from_stack
                assert card and card['rank'] == cs.ACE
                self._score = 90000
                if from_stack in rows:
                    self._score += (4 - from_stack.cap['base_rank'])
                self._score += 100 * (self.K - card['rank'])
                self.add_hint(ncards, from_stack, to_stack)
            for pile in self.pile_sequences(from_stack):
                if pile:
                    self.step010_move_pile(from_stack, pile, rows)

    def step010_move_pile(self, from_stack, pile, rows):
        """
        step010_move_pile(from_stacklist(Stack ...), pile:list(Card ...),
                          rows:list(Stack ...)) -> None

        Calculate and score **pile** build **rows**. (60000 .. 99999)

        Override **from_stack** build **rows** and **rows** not in sequence,
        sum score with preference **dropstacks** in **rows** then **add_hint**
        for first possible move.
        """
        self._score = 8000
        # Check for correct sequence on **rows**.
        if from_stack in rows:
            fc_stack = hint.AClonedStack.clone(stack, cards=stack.cards[:-1])
            # Do not move a card that is already in correct place
            if fc_stack.acceptsCards(None, pile):
                return
            self._score += (4 - from_stack.cap['base_rank'])
        # Find **rows** build **dropstacks**
        for to_stack in self._seq_builds_iter(rows, stack, pile):
            self._score += 100 * (self.K - pile[0]['rank'])
            self.add_hint(1, from_stack, to_stack)
            break

    def step040(self, rows, reserves):
        """
        step040(rows:list(Stack ...), reserves:list(Stack ...)) -> None

        Calculate and score **rows** build on **reserves**. (10000 .. 19999)

        Overide  **rows** builds **reserves**, sum score base on comparison of
        cards with greater inital score then **add_hint** with for first
        possible move.
        """
        args = self.pile_builds(from_stacks=rows, to_stacks=reserves)
        if args:
            self._score = 70000 + 100 * (self.K - pile[0]['rank'])
            self.add_hint(len(args[2]), args[0], args[1])


class CautiousHint(CautiousHintMethods, Hint):
    pass


class PasDeDeux_Hint(AbstractHint):

    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        getDistance = lambda s, c: sum([int(c['rank'] != s.id % 13),
                                        int(c['suit'] != s.id / 13)])
        game = self.game
        rows = self._stacks['set']['rows']
        talon = self._stacks['set']['talon']
        waste = self._stacks['set']['talon']
        for from_stack in (s for s in rows if s.cardsPlay(s.cards)):
            i = from_stack.id
            for to_stack in (s for s in rows[i % 13::13] + rows[i:i + 13]
                               if s is not from_stack):
                assert to_stack.cardsBuild(from_stack, from_stack.cards)
                to_card, from_card = to_stack.TOPCARD, from_stack.TOPCARD
                rw = 3 if talon.round >= 2 else 4
                if any((card._id == to_card._id - 52) for card in waste.cards):
                    rw -= 1
                score = int(((getDistance(from_stack, from_card) * rw +
                              getDistance(to_stack, to_card) * 2) -
                             (getDistance(to_stack, from_card) * rw +
                              getDistance(from_stack, to_card) * 2)) * 1000)
                if score > 0:
                    self.add_hint(score, 1, from_stack, to_stack)
            break


class AceOnly_StackMethod(object):
    """
    Build Ace. Play none. Flip cards when full seq on pile.
    """
    _BOTTOM_IMAGE_ = 'rank'

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj, suit:int[, offset:tup=(0, YOFFSET),
          *capability* {base_rank:int=ACE, dir:int=0, max_move:int=0,
          max_cards:int=SUITS}]) -> None

        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        SUITS = len(game['suits']) * game['decks']
        kwdefault(cap, base_rank=cs.ACE, dir=0, max_move=0, max_cards=SUITS,
                  offset=(0, self.game.images.offset.yoffset))
        super(AceOnly_StackMethod, self).__init__(x, y, game, **cap)

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard a flipAllMove and
        is_filled on pile with max_cards.
        """
        if len(self.cards) == self.cap['max_cards']:
            self.is_filled = True
            game = self.game
            if game.move_state not in (game.S_REDO, game.S_RESTORE):
                game._move_do(a_move.AFlipAllMove(self))  # low level

    def cardFlips(self):
        """
        cardFlips() -> bool

        :return: flip |top|?
        :rtype: boolean
        """
        return False


class StackMethods(stack.FinishCloseMethod, stack.NotFromTalonBuildMethod):
    """
    Build from any but _talon_. Shade pile when full seq on pile.
    """
    pass


class Swap_StackMethods(object):

    def move_pile(self, ncards, to_stack, **kwargs):
        """
        move_pile(ncards:int, to_stack:obj [, frames:int=-1, shadow:int=-1])
          -> None

        Swap cards with play pile _row_, if cards. else parent method.

        :param integer ncards: number of cards from top of pile.
        :param object to_stack: receiving pile.
        """
        if to_stack.cards:
            assert ncards == 1
            self.game._move_do(a_move.ASwapPairMove(self, to_stack, **kwargs),
                               reset=True)  # low level
        else:
            super(Swap_StackMethods, self).move_pile(ncards, to_stack, **kwargs)


class VirginiaReel_Talon(stack.DealRowTalon):
    """
    Deal 1 card on _row_ while _tableaux_ not gap.
    """

    def can_deal_cards(self):
        """
        can_deal_cards() -> bool

        :return: deal more cards?
        :rtype: True | False
        """
        if not super(VirginiaReel_Talon, self).can_deal_cards():
            return False
        return all(bool(s.cards) for s in self.game.s['tableaux'])


class PasDeDeux_Waste(stack.WasteStack):
    """
    Build none. Flip none.
    """

    def cardFlips(self):
        """
        cardFlips() -> bool

        :return: flip |top|?
        :rtype: boolean
        """
        return False


class AceOnly_RK_Foundation(AceOnly_StackMethod, stack.RK_Foundation):
    """
    Build Ace. Play none. Flip cards when full seq on pile.
    """
    pass


class AceOnly_AC_Foundation(AceOnly_StackMethod, stack.SC_Foundation):
    """
    Build Ace by alt color. Play none. Flip cards when full seq on pile.
    """
    pass


class TableauStack(stack.SS_RowStack):
    """
    Build in rank by same suit. Play seq. _BOTTOM_IMAGE_ is rank.
    """
    _BOTTOM_IMAGE_ = 'rank'


class Swap_Tableau(Swap_StackMethods, TableauStack):
    """
    Swap pair by same rank with pile of same cap['base_rank']. Play seq.
    _BOTTOM_IMAGE_ is rank.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check _canSwapPair() or parent.cardsBuild().

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        if not super(Swap_Tableau, self).cardsBuild(from_stack, cards):
            return False
        return (from_stack.BASECARD['rank'] == self.cap['base_rank'] and
                self.BASECARD['rank'] == from_stackcap['base_rank'] and
                from_stack in self.game.s['tableaux'])


class RowStack(StackMethods, stack.BasicRowStack):
    """
    Build none from any but _talon_. Play top. Shade pile when full seq on
    pile.
    """
    pass


class SS_RowStack(StackMethods, stack.SS_RowStack):
    """
    Build down in rank by same suit from any but _talon_. Play top. Shade pile
    when full seq on pile.
    """
    pass


class Zeus_RowStack(stack.SS_RowStack):
    """
    Build down by rank in same suit. Play seq. Base QUEEN or KING.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check parent.cardsBuild() and base card QUEEN or KING.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        if not super(Zeus_RowStack, self).cardsBuild(from_stack, cards):
            return False
        return self.cards or cards[0]['rank'] in (cs.QUEEN, cs.KING)


class PasDeDeux_RowStack(Swap_StackMethods, stack.ReserveStack):
    """
    Build any 1 card from same row or col. Play top with same rank and same
    suit as _waste_.
    """

    def cardsPlay(self, cards):
        """
        cardsPlay(cards:tup) -> bool

        Checks *capability* for swapping cards with waste top card of same
        suit and rank.

        :param tuple cards: cards to move from this pile
        :return: cards moveable?
        :rtype: boolean
        """
        if not super(PasDeDeux_RowStack, self).cardsPlay(cards):
            return False
        card = self.game.s['wastes'][-1].TOPCARD
        return (card and card.face_up and
                cmp_cards.isSameSuitSequence([card] + cards, dir=0))

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Check parent.cardsBuild() and Card from same row or col.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts ``cards``?
        :rtype: boolean
        """
        if not super(PasDeDeux_RowStack, self).cardsBuild(from_stack, cards):
            return False
        return any(t == r for r, t in zip(divmod(self.id, 13),
                                          divmod(from_stack.id, 13)))

    def move_pile(self, ncards, to_stack, **kw):
        """
        move_pile(ncards:int, to_stack:obj [, frames:int=-1, shadow:int=-1])
          -> None

        Swap 1 card card with _waste_, then deal _waste_ 1 card.

        :param integer ncards: number of cards from top of pile.
        :param object to_stack: receiving pile.
        :raises: AssertionError: Class is not root class method.
        """
        assert ncards == 1 and to_stack in self.game.s['rows']
        super(PasDeDeux_RowStack).move_pile(ncards, to_stack, **kw)
        if not self.game.talon_deal():
            # mark game as complete by turning the Waste face down
            assert self.game.s['wastes'][-1].TOPCARD.face_up
            self.game.s['wastes'][-1].flip_card()

    def closeStack(self):
        """
        closeStack() -> None

        Overide to perfom after addCard or insertCard set _is_filled when
        rank and suit match pile location.
        """
        c = self.BASECARD
        if len(self.cards) == 1 and (c['suit'], c['rank']) == divmod(self.id, 13):
            self.is_filled = True

    def getBottomImage(self):
        """
        getBottomImage() -> obj

        Overide _BOTTOM_IMAGE_ to set a suit bottom image for each row of
        _row_.

        :return: pile's suit base image.
        :rtype: Image
        """
        return self.game.images.bottom('suit', self.id / 13)

    def handle_quickplay(self, event, from_stacks=None, to_stacks=None):
        """
        Quick play. Override to only use rows with just from_pile method.

        *note:*
          - Use from_stacks and to_stacks for subclass with different piles.
          - Calls _quick_from_pile method only.

        .. handle_quickplay(event:obj, *,
             from_stacks:seq(Pile ...)=sg[dropstacks] + s[foundations],
             to_stacks:seq(Pile ...)=sg[dropstacks] + s[foundations]) -> None

        *Parameters:*
          event: {Event} None.
          from_stack: seq(Pile ...) sg[dropstacks] + s[foundations]
            Build a sequence from a pile. *AS_KEYWORD_ONLY*
          to_stack: seq(Pile ...) sg[dropstacks] + s[foundations]
            Play a sequence to a pile. *AS_KEYWORD_ONLY*
        """
        from_stacks = from_stacks or self.game.s['row']
        moves = self._quick_from_pile(from_stacks, event=event)
        if moves:
            ncards, from_pile, to_pile = moves[max(moves)]
            Logger.info("move (%s)", moves)
            assert to_pile.cardsBuild(from_pile, from_pile.cards[-ncards:])
            self.game.move(ncards, from_pile, to_pile)
            return True
        return False


class PictureGallery(game.Game):
    """
    Arrange cards on _tableau_ and finish ACE on _foundation_ within a turn.

    Layout
      - tableau [24 over 3 row NW, vert offset]
      - row [10 horz SW, vert offset]
      - talon [1 SE, no offset]
      - foundation [1 NE, no offset]

    _tableaux_
      Pile 24 over 3 row. Init deal 1 card. Build in rank by 3: from FOUR N;
      from THREE center; from TWO S. Top Play.

    _row_
      Pile 8 horz S _tableaux_. Init deal 1 card. Gap deal 1 card from
      _talon_. Play top.

    _talon_
      Pile 1 E _row_. Deal 1 card _row_.

    _foundation_
      Pile 1 E _tableaux_. Pair ACE. Play none.

    note::
      ⟪highlight_play⟫ disabled.
    ------
    :cvar Stack _talon_: stack.DealRowTalon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: AceOnly_RK_Foundation
    :cvar Stack _row_: RowStack (max_accept=1)
    :cvar Stack _tableux_: TableauStack (max_cards=4, dir=3, base_rank=FOUR,
      max_accept=1), TableauStack (max_cards=4, dir=3, base_rank=THREE,
      max_accept=1), TableauStack (max_cards=4, dir=3, base_rank=TWO,
      max_accept=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=7, name='Picture Gallery', game_type=(GT.TWO_DECK |
                    GT.POPULAR | GT.ATARI | GT.KDE), decks=2, redeals=0,
                    skill_level=SL.BALANCED, altnames=('Die Bildgallerie',
                    'Mod-3'), version='1.02')
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = stack.DealRowTalon
    _waste_ = None
    _foundation_ = AceOnly_RK_Foundation
    _tableaux_ = (StackWrapper(TableauStack, max_cards=4, dir=3,
                               base_rank=cs.FOUR, max_accept=1),
                  StackWrapper(TableauStack, max_cards=4, dir=3,
                               base_rank=cs.THREE, max_accept=1),
                  StackWrapper(TableauStack, max_cards=4, dir=3,
                               base_rank=cs.TWO, max_accept=1),)
    _row_ = StackWrapper(RowStack, max_accept=1)
    _hint_ = Hint

    def _create_game(self, **kw):
        l, s = Layout(self), self.s
        dy = l._get_stack_height((12 / len(self._tableaux_) - 1), l.YS)
        self.table_size = (l.XM + (l.NSUITS + 2) * l.XS,
                     2 * l.YM + 3 * dy + l._get_stack_height(9, l.YS),)
        s['tableaux'] = ()
        y = l.YM
        for _tableau_ in zip(self._tableaux_):  # Vert
            x = l.XM
            for j in range(l.NSUITS):  # Horz
                s['tableaux'].append(_tableau_(x, y, game=self,
                                            offset=(0, l.YOFFSET)))
                x += l.XS
            y = dy
        x = l.XM
        y += l.YM
        for i in range(l.NSUITS):
            s['rows'].append(self._row_(x, y, game=self))
            x += l.XS
        x = self.width - 3 * l.XS / 2
        y = l.YM + l.YS / 2
        s['foundations'].append(self._foundation_(x, y, game=self))
        y = self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'se')
        if self._waste_ is not None:
            y -= l.YS
            s['wastes'].append(self._waste_(x, y, game=self))
            l.set_ncards_text(s['wastes'][-1], 'se')
        self._set_region(s['foundations'], l._region(x=x - l.HCW, h=y - l.CH))
        self.defaultStackGroups()
        self.sg['openstacks'] += s['tableaux']
        self.sg['dropstacks'] += s['tableaux']
        self.sg['reservestacks'] = s['rows']

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[tableaux] 1 ea.
          - s[rows] 1 ea.

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['tableaux'], frames=0)
        self.s['talon'].dealRow()

    @property
    def is_complete(self):
        return all(s._is_filled for s in self.s['tableaux'] + self.s['foundations'])

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill stack if _row_ when gap from _talon_.

        *Parameters*:
          stack : Stack
            pile just played
        """
        fill.fromTalon(self, stack, self.s['rows'])

    @property
    def _highlight_piles(self):
        """
        Get stacks to highlight for possible move.

        :return: empty.
        :rtype: tuple
        """
        return ()


class GreatWheel(PictureGallery):
    """
    Arrange cards _tableau_ and finish ACE on _foundation_ within a turn.

    _tableaux_
      Pile 16 over 2 row. Init deal 1 card. Build in rank by 2: from THREE N;
      from TWO S. Top Play.

    _row_
      Pile 8 horz S _tableaux_. Init deal 4 card. Build in rank by same suit
      when _talon_ has no cards. Gap deal 4 card from _waste_. Play top.

    _talon_
      Pile 1 E _row_. Deal 1 card _waste_.

    _waste_
      Pile 1 N _talon_. Build none. Play top.

    _foundation_
      Pile 1 E _tableaux_. Pair ACE in alt color. Play none.

    note::
      ⟪highlight_play⟫ disabled.
    ------
    :cvar Stack _talon_: stack.WasteTalon
    :cvar Stack _waste_: stack.WasteStack
    :cvar Stack _foundation_: AceOnly_AC_Foundation
    :cvar Stack _row_: SS_RowStack (max_accept=1, dir=1)
    :cvar Stack _tableux_: TableauStack (max_cards=6, dir=2, base_rank=THREE,
      max_accept=1), TableauStack (max_cards=5, dir=2, base_rank=TWO,
      max_accept=1)
    :cvar Hint _hint_: CautiousHint
    """
    __info__ = dict(id=397, name='Great Wheel', game_type=GT.TWO_DECK,
                    decks=2, redeals=0, skill_level=SL.BALANCED,
                    ranks=tuple(range(cs.QUEEN + 1)), version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.WasteTalon
    _waste_ = stack.WasteStack
    _foundation_ = AceOnly_AC_Foundation
    __tableaux_ = (StackWrapper(TableauStack, max_cards=6, dir=2,
                                max_accept=1, base_rank=cs.THREE),
                   StackWrapper(TableauStack, max_cards=5, dir=2,
                                max_accept=1, base_rank=cs.TWO),)
    _row_ = StackWrapper(SS_RowStack, max_accept=1, dir=1)
    _hint_ = CautiousHint

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill stack if _row_ when gap from _waste_ with 4 cards.

        *Parameters*:
          stack : Stack
            pile just played
        """
        for i in range(4):
            fill.fromWaste(self, stack, self.s['rows'])

    def _start(self, rows=4, flip=True, **kw):
        """
        Deal initial cards for new game.
          - s[rows] 4 ea up.

        .. \_start([rows=4, flip=True]) -> None
        """
        for i in range(rows - 1):
            self.s['talon'].dealRow(flip=flip, frames=0)
        self.s['talon'].dealRow()


class RoyalParade(PictureGallery):
    """
    Arrange cards _tableau_ and finish ACE on _foundation_ within a turn.

    _tableaux_
      Pile 24 over 3 row. Init deal 1 card. Build in rank by 3: from TWO N;
      from THREE center; from FOUR S. Top Play. Any 2 base may swap when top
      and rank is base rank of other.

    _row_
      Pile 8 horz S _tableaux_. Init deal 1 card. Gap deal 1 card from
      _talon_. Play top.

    _talon_
      Pile 1 E _row_. Deal 1 card _row_.

    _foundation_
      Pile 1 E _tableaux_. Pair ACE. Play none.

    note::
      ⟪highlight_play⟫ disabled.

    seealso::
      <http://en.wikipedia.org/wiki/Virginia_Reel_%28solitaire%29>
    ------
    :cvar Stack _talon_: stack.DealRowTalon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: AceOnly_RK_Foundation
    :cvar Stack _row_: stack.BasicRowStack (max_accept=0)
    :cvar Stack _tableux_: TableauStack (max_cards=4, dir=3, base_rank=TWO,
      max_accept=1), TableauStack (max_cards=4, dir=3, base_rank=THREE,
      max_accept=1), TableauStack (max_cards=4, dir=3, base_rank=FOUR,
      max_accept=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=546, name='Royal Parade', game_type=GT.TWO_DECK,
                    decks=2, redeals=0, skill_level=SL.MOSTLY_SKILL,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _tableaux_ = (StackWrapper(Swap_Tableau, base_rank=cs.TWO, max_cards=4,
                               dir=3, max_accept=1),
                  StackWrapper(Swap_Tableau, base_rank=cs.THREE, max_cards=4,
                               dir=3, max_accept=1),
                  StackWrapper(Swap_Tableau, base_rank=cs.FOUR, max_cards=4,
                               dir=3, max_accept=1),)
    _row_ = StackWrapper(stack.BasicRowStack, max_accept=0)

    def _create_game(self, **kw):
        l, s = super(RoyalParade, self)._create_game(**kw)
        s['internals'].append(stack.InvisibleStack(self))

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[tableaux] 1 ea.
          - s[rows] 1 ea.

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['tableaux'])
        self.s['talon'].dealRow()


class VirginiaReel(RoyalParade):
    """
    Arrange cards _tableau_ and finish ACE on _foundation_ within a turn.

    _tableaux_
      Pile 24 over 3 row. Init deal 1 card with first of row deal its base.
      Build in rank by 3: from TWO N; from THREE center; from FOUR S. Top
      Play. Any 2 base may swap when top and rank is base rank of other.

    _row_
      Pile 8 horz S _tableaux_. Init deal 1 card. Build 1 card. Play top.

    _talon_
      Pile 1 E _row_. Deal 1 card _row_ when _tableaux_ has cards.

    _foundation_
      Pile 1 E _tableaux_. Pair ACE. Play none.

    note::
      ⟪highlight_play⟫ disabled.

    seealso::
      <http://en.wikipedia.org/wiki/Virginia_Reel_%28solitaire%29>
    ------
    :cvar Stack _talon_: VirginiaReel_Talon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: AceOnly_RK_Foundation
    :cvar Stack _row_: stack.BasicRowStack (max_accept=0)
    :cvar Stack _tableux_: TableauStack (max_cards=4, dir=3, base_rank=TWO,
      max_accept=1), TableauStack (max_cards=4, dir=3, base_rank=THREE,
      max_accept=1), TableauStack (max_cards=4, dir=3, base_rank=FOUR,
      max_accept=1)
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=547, name='Virginia Reel', game_type=GT.TWO_DECK,
                    decks=2, redeals=0, skill_level=SL.MOSTLY_SKILL,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __credits__ = ['Albert Morehead', 'Geoffrey Mott-Smith']
    __version__ = 7.70

    _talon_ = VirginiaReel_Talon

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return single FOUR, THREE and TWO on top by rank, suit for
        _row_ deal.
        """
        cards = place.MoveToTop(cards, place.AllFour, ncards=1)
        cards = place.MoveToTop(cards, place.AllThree, ncards=1)
        return place.MoveToTop(cards, place.AllTwo, ncards=1)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - TODO

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['tableaux'][0::8], frames=0)
        for i, j in zip(range(1, 26, 8), range(8, 32, 8)):
            self.s['talon'].dealRow(rows=self.s['tableaux'][i:j])
        self.s['talon'].dealRow()

    def _auto_fill(self, stack):
        """
        Never fill stacks.

        *Parameters*:
           stack : Stack
             pile just played
        """
        pass


class MountOlympus(game.Game):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 9 horz S _foundation_. Init deal 1 card. Build down in rank by same
      suit by 2. Play seq. Gap deal 1 card from _talon_. Play top.

    _talon_
      Pile 1 E _row_. Deal 1 card _row_.

    _foundation_
      Pile 8 pile over 2 row. Init deal base ACE N and TWO s. Build up in rank
      by same suit by 2. Play none.

    seealso::
      <http://en.wikipedia.org/wiki/Mount_Olympus_%28solitaire%29>
    ------
    :cvar Stack _talon_: stack.DealRowTalon
    :cvar Stack _foundation_: stack.SS_Foundation (dir=2, max_move=0,
      max_cards=7), stack.SS_Foundation (dir=2, max_move=0, max_cards=6,
      base_rank=TWO)
    :cvar Stack _row_: stack.SS_RowStack, dir=-2
    """
    __info__ = dict(id=398, name='Mount Olympus', game_type=GT.TWO_DECK,
                    decks=2, redeals=0, skill_level=SL.BALANCED,
                    version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.DealRowTalon
    _foundations_ = (StackWrapper(stack.SS_Foundation, dir=2, max_move=0,
                                 max_cards=7),
                     StackWrapper(stack.SS_Foundation, dir=2, max_move=0,
                                 max_cards=6, base_rank=cs.TWO),)
    _row_ = StackWrapper(stack.SS_RowStack, dir=-2)

    def _create_game(self, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + 9 * l.XS, l.YM + 2 * l.YS, l._get_stack_height(12, l.YS),)
        x, y = l.XM + l.XS, l.YM
        for _foundation_ in self._foundations_:
            for i in l.SUITS:
                s['foundations'].append(_foundation_(x, y, game=self, suit=i))
                x += l.XS
            y += l.YS
        x = l.XM
        for i in range(9):
            s['rows'].append(self._row_(x, y, game=self))
            x += l.XS
        s['talon'] = self._talon_(l.XM, l.YM, game=self)
        l.set_ncards_text(s['talon'], 's')
        l.defaultStackGroups()
        return l

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return ACE and TWO on top by rank, suit for _foundation_
        deal.
        """
        return place.MoveToTop(cards, place.AllAceTwo)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[foundations] 1 ea.
          - s[rows] 1 ea up.

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['foundations'], frames=0)
        self.s['talon'].dealRow()

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill stack if _row_ when gap from _talon_.

        *Parameters*:
          stack : Stack
            pile just played
        """
        fill.fromTalon(self, stack, self.s['rows'])


class Zeus(MountOlympus):
    """
    Finish cards on _foundation_ within a turn.

    _row_
      Pile 9 horz S _foundation_. Init deal 4 open card. Build down in rank by
      same suit by 2. Play seq. Gap deal 1 card from _talon_ else base QUEEN
      or KING. Play top.

    _talon_
      Pile 1 E _row_. Deal 1 card _row_.

    _foundation_
      Pile 8 pile over 2 row. Init deal base ACE N and TWO s. Build up in rank
      by same suit by 2. Play none.
    ------
    :cvar Stack _talon_: stack.DealRowTalon
    :cvar Stack _foundation_: stack.SS_Foundation (dir=2, max_move=0,
      max_cards=7), stack.SS_Foundation (dir=2, max_move=0, max_cards=6,
      base_rank=TWO)
    :cvar Stack _row_: Zeus_RowStack
    """
    __info__ = dict(id=399, name='Zeus', game_type=GT.TWO_DECK, decks=2,
                    redeals=0, skill_level=SL.BALANCED, version='0.90fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = Zeus_RowStack

    def _start(self, rows=4, flip=True, **kw):
        """
        Deal initial cards for new game.
          - s[foundations] 1 ea.
          - s[rows] 4 ea up.

        .. \_start([rows=4, flip=True]) -> None
        """
        self.s['talon'].dealRow(rows=self.s['foundations'], frames=0)
        for i in range(rows - 1):
            self.s['talon'].dealRow(flip=flip, frame=0)
        self.s['talon'].dealRow()


class PasDeDeux(game.Game):
    """
    Arrange cards on _row_ within a turn. Swap card with another in col or row
    when same card as _waste.

    _row_
      Pile 52 over 4 row. Init deal 1 card from 1st deck.

    _talon_
      1 pile SE. Init 2nd deck. Deal 1 card on _waste_.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build none. Play none.

    note::
      ⟪autodrop⟫ disabled
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=2)
    :cvar Stack _waste_: PasDeDeux_Waste (max_move=0)
    :cvar Stack _row_: PasDeDeux_RowStack (max_cards=1, max_accept=1)
    :cvar Hint _hint_: PasDeDeux_Hint
    """
    __info__ = dict(id=153, name='Pas de Deux', game_type=(GT.MONTANA |
                    GT.SEPARATE_DECKS), decks=2, redeals=1,
                    skill_level=SL.MOSTLY_SKILL, version='3.40')
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = StackWrapper(stack.WasteTalon, max_rounds=2)
    _waste_ = StackWrapper(PasDeDeux_Waste, max_move=0)
    _row_ = StackWrapper(PasDeDeux_RowStack, max_cards=1, max_accept=1)
    _hint_ = PasDeDeux_Hint

    def _create_game(self, rows=(13,) * 4, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + max(rows) * l.XS, l.YM + (len(rows) + 1) * l.YS)
        y = l.YM
        for row in rows:
            x = l.XM
            for i in range(row):
                s['rows'].append(self._row_(x, y, game=self))
                x += l.XS
            y += l.YS
        x, y = self.width - 2 * l.XS, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'se')
        l.set_rounds_text(s['talon'], 'ne')
        x -= l.XS
        s['wastes'].append(self._waste_(x, y, self))
        l.set_ncards_text(s['wastes'][-1], 'sw')
        s['internals'].append(stack.InvisibleStack(self))
        l.defaultStackGroups()

    def _pack_shuffle(self, cards, parts=None):
        """
        _pack_shuffle(cards:seq(Card ...)[, parts:int=NumDecks])
        -> seq(Card ...)

        Override to returns given cards shuffled by deck given random. Allows
        for common shuffle that does other parts within a pack seperate. May
        overide to modify shuffle method.
        """
        parts = parts or self['decks']
        return super(PasDeDeux, self)._pack_shuffle(cards, parts)

    def _start(self, **kw):
        """
        Deal initial cards for new game.
          - s[rows] 1 ea.
          - talon deal.

        .. \_start([]) -> None
        """
        self.s['talon'].dealRow()
        self.talon_deal(sound=False)

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autoflip, autodrop.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        return autostacks.quickplay(super(PasDeDeux, self)._autopiles(event))

    @property
    def is_complete(self):
        return all(r._is_filled for r in self.s['rows'])

    @property
    def _highlight_piles(self):
        """
        Get stacks to highlight for possible move.

        :return: `s.rows` >= 1.
        :rtype: tuple
        """
        return ((self.s['rows'], 1),)


PROVIDES = [PasDeDeux, PictureGallery, GreatWheel, MountOlympus, Zeus,
            RoyalParade, VirginiaReel]